﻿using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using XTrace.Registry;

namespace XTrace
{
	public enum RelativePath
	{
		ThrowException,
		CurrentDir,
#if !PocketPC
		EntryAssemblyLocation,
#endif
		TempFolder,
		// PublicUser
		// LocalAppData,
	}

	public class StructuredTextFileXTraceListener : XTraceListener
	{
		StreamWriter _writer;
		Stream _stream;
		public string ActualCurrentFileName { get; private set; }

		public StructuredTextFileXTraceListener(string path)
			: this(path, RelativePath.ThrowException)
		{
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="path"></param>
		/// <param name="relativePath">Autoresolve relative path to</param>
		public StructuredTextFileXTraceListener(string path, RelativePath relativePath)
		{
			if (!Path.IsPathRooted(path))
			{
				switch (relativePath)
				{
					case RelativePath.ThrowException:
						throw new ArgumentException(@"Path is not rooted", "path");
					case RelativePath.CurrentDir:
						path = Path.Combine(Directory.GetCurrentDirectory(), path);
						break;
#if !PocketPC
					case RelativePath.EntryAssemblyLocation:
						path = Path.Combine(Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "_XTraceLogs"), path);
						break;
#endif
					case RelativePath.TempFolder:
						path = Path.Combine(Path.Combine(Path.GetTempPath(), "_XTraceLogs"), path);
						break;
					default:
						throw new ArgumentOutOfRangeException("relativePath");
				}
			}

			if (string.IsNullOrWhiteSpace(Path.GetExtension(path)))
			{
				path += ".xlog";
			}

			_filePath = path;

		}

		readonly object _sync = new object();
		private int _filesCount = _defaultFilesCount;

		public int FilesCount
		{
			get { return _filesCount; }
			set { _filesCount = value; }
		}
		private long _sizeLimit = _defaultSizeLimit;

		public long SizeLimit
		{
			get { return _sizeLimit; }
			set { _sizeLimit = value; }
		}

		private XTraceLevel _levelLimit = XTraceLevel.All;
		public XTraceLevel LevelLimit
		{
			get { return _levelLimit; }
			set { _levelLimit = value; }
		}

		private string _filePath;
		private const int _defaultFilesCount = 3;
		private const int _defaultSizeLimit = 1024 * 1024 * 50; // 50MB

		#region Rotation

		// Encoding _encoding = new UTF8Encoding(false, true);

		public void ForceInit()
		{
			lock (_sync)
			{
				EnsureWriter();
			}
		}

		private bool EnsureWriter()
		{
			var ok = true;
			if (_writer == null)
			{
				ok = false;
				if (_filePath == null)
				{
					return false;
				}
				string fullPath = Path.GetFullPath(_filePath);
				string directoryName = Path.GetDirectoryName(fullPath);
				string fileName = Path.GetFileName(fullPath);

				for (int i = 0; i < 2; i++)
				{
					try
					{
						Directory.CreateDirectory(directoryName);

						var targetFileName = RecycleLogFiles(directoryName, fileName);

						if (File.Exists(targetFileName))
						{
							if (SourcesProvider.FilesToRemove.Any(x => string.Equals(x, targetFileName, StringComparison.InvariantCultureIgnoreCase)))
							{
								//try to remove
								SourcesProvider.MarkForClear(targetFileName);
							}
						}

						_stream = File.Open(targetFileName, FileMode.Append, FileAccess.Write, FileShare.Read);
						_writer = new StreamWriter(_stream);
						ActualCurrentFileName = targetFileName;
						SourcesProvider.RegistrerFile(targetFileName);
						ok = true;
						break;
					}
					catch (IOException)
					{
						fileName = Guid.NewGuid() + fileName;
						//fullPath = Path.Combine(directoryName, fileName);
					}
					catch (UnauthorizedAccessException)
					{
						break;
					}
					catch (Exception)
					{
						break;
					}
				}
				if (!ok)
				{
					_filePath = null;
				}
			}
			else
			{
				// notice only after flushing...
				if (_sizeLimit > 0 && _stream.Length >= _sizeLimit)
				{
					DisposeImpl();
					ok = EnsureWriter();
				}
			}

			return ok;
		}

		private static string GenerateName(string directoryName, string fileName, int num)
		{
			return GenerateName(directoryName, fileName, num.ToString("0", CultureInfo.InvariantCulture));
		}

		private static string GenerateName(string directoryName, string fileBaseName, string num)
		{
			string resultFileName = Path.GetFileNameWithoutExtension(fileBaseName) + "_" + num + Path.GetExtension(fileBaseName);
			if (directoryName != null)
			{
				return Path.Combine(directoryName, resultFileName);
			}
			return resultFileName;
		}

		private string RecycleLogFiles(string directoryName, string fileBaseName)
		{
			var fileNames = Directory.GetFiles(directoryName, GenerateName(null, fileBaseName, "*"));
			var files = fileNames.Select(x => new FileInfo(x)).OrderBy(x => x.LastWriteTime.ToUniversalTime()).ToArray();

			string resultName;

			// самый свежий
			var last = files.LastOrDefault();

			if (last == null)
			{
				// файлов нет, создаём первый файл
				resultName = GenerateName(directoryName, fileBaseName, 1);
			}
			else if (_sizeLimit == 0 || last.Length < _sizeLimit)
			{
				// продолжаем писать в последний файл, там еще есть место, либо место не ограничено
				resultName = last.FullName;
			}
			// последний файл переполнен :
			else if (files.Length >= _filesCount)
			{
				// количество файлов достигло максимума - пишем вместо самого старого
				var first = files.FirstOrDefault();
				resultName = first.FullName;
				SourcesProvider.MarkForClear(resultName);
			}
			else
			{
				// еще есть слоты для файлов, но последний файл переполнен - пишем в следующий
				// todo если кто-то удалит средний файл из набора - будет не очень хорошо
				resultName = GenerateName(directoryName, fileBaseName, files.Length + 1);
			}

			return resultName;
		}

		#endregion

		#region Overrides of XTraceListener

		public override void Event(XTraceEvent traceEvent)
		{
			if (traceEvent.Level <= LevelLimit)
			{
				lock (_sync)
				{
					if (EnsureWriter())
					{
						_writer.WriteLine(traceEvent.Serialize());
						_writer.Flush();
					}
				}
			}
		}

		#endregion

		protected override void DisposeImpl()
		{
			ActualCurrentFileName = null;
			base.DisposeImpl();
			var wr = _writer;
			if (wr != null)
			{
				wr.Close();
				_writer = null;
			}
			var stream = _stream;
			if (stream != null)
			{
				stream.Close();
				_stream = null;
			}
		}
	}
}